/**************************************************************************
 *
 *   Copyright 2010 American Public Media Group
 *
 *   This file is part of AIR2.
 *
 *   AIR2 is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   AIR2 is distributed in the hope that it will be useful, 
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License 
 *   along with AIR2.  If not, see <http://www.gnu.org/licenses/>.
 *
 *************************************************************************/
 
Ext.ns('AIR2.Search.Advanced');
Ext.ns('AIR2.Search.Advanced.Source');

// custom Vtype for vtype:'Integer'
Ext.apply(Ext.form.VTypes, {
    Integer:  function(v) {
        return /^[\d\?\*]+$/.test(v);   // allow wildcards
    },
    IntegerText: 'Must be an integer'
});

AIR2.Search.Advanced.EXPLAIN = 'adv-search-query-explain';
AIR2.Search.Advanced.BOOLEAN = 'adv-search-master-boolean';
AIR2.Search.Advanced.FORMID  = 'adv-search-form';

// See note in fixtures.js about user_* vs src_* vs *
AIR2.Search.Advanced.Fields = {
  'sources' : [
    { name: 'cact_action_detail'        , type: 'string' },
    { name: 'cact_action_notes'         , type: 'string' },
    { name: 'activity_id'               , type: 'auto',
      list: function() { return AIR2.Fixtures.activities }
    },
    { name: 'card_value'                , type: 'string' },
    { name: 'email'                     , type: 'string' },
    { name: 'cexp_exp_id'               , type: 'string' },
    { name: 'city'                      , type: 'string' },
    { name: 'state'                     , type: 'auto', 
      list: function() { return AIR2.Fixtures.states } 
    },
    { name: 'zip'                       , type: 'string' },
    { name: 'src_description'           , type: 'string' },
    { name: 'birth_year'                , type: 'int' },
    { name: 'src_education_level'       , type: 'auto', 
      list: function() { return AIR2.Facts.education_level } 
    },
    { name: 'src_employer'              , type: 'string' },
    //{ name: 'src_experiences'           , type: 'string' },
    { name: 'src_first_name'            , type: 'string' },
    
    
    { name: 'user_gender'               , type: 'auto', 
      list: function() { return AIR2.Facts.gender } 
    },
    { name: 'gender'                    , type: 'string' },
    { name: 'src_gender'                , type: 'auto', 
      list: function() { return AIR2.Facts.gender } 
    },
    
    // income is always a source-mapped value
    { name: 'src_household_income'     , type: 'auto', 
      list: function() { return AIR2.Facts.household_income }, 
      sortBy: "key" 
    },
    { name: 'src_last_name'             , type: 'string' },
    { name: 'src_login_name'            , type: 'string' },  // in AIR1 always same as primary email
    { name: 'src_organizations'         , type: 'string' },
    { name: 'src_political_affiliation' , type: 'auto', 
      list: function() { return AIR2.Facts.political_affiliation } 
    },
    { name: 'lifecycle'                 , type: 'auto', 
      list: function() { return AIR2.Facts.lifecycle } 
    },
    { name: 'political_offices'         , type: 'string' },
    { name: 'pref_lang'                 , type: 'auto', 
      list: function() { return [['NULL','None'],['Spanish','Spanish'],['English','English']] }  // TODO programmatic?
    },
    { name: 'src_prof_title'            , type: 'string' },
    { name: 'race'                      , type: 'auto', 
      list: function() { return AIR2.Facts.race } 
    },
    { name: 'race_text'                 , type: 'string' },
    { name: 'religion'                  , type: 'auto', 
      list: function() { return AIR2.Facts.religion } 
    },
    { name: 'religion_text'             , type: 'string' },
    { name: 'src_uuid'                  , type: 'string' },
    { name: 'outgoing_activity_text'    , type: 'string' }, // TODO used?
    { name: 'pin_status'                , type: 'auto', 
      list: function() { return AIR2.Fixtures.status_names } 
    },
    { name: 'form'                      , type: 'form', label: 'Query' },
    { name: 'first_responded_date'      , type: 'date'},
    { name: 'last_responded_date'       , type: 'date'},
    { name: 'last_contacted_date'       , type: 'date'},
    { name: 'last_activity_date'        , type: 'date'},
    { name: 'valid_email'               , type: 'auto',
      mapping: function() { return [['0','False'],['1','True']] }
    }
  ]
  
  // TODO other IDX categories
  
};

AIR2.Search.Advanced.RemoveButton = function() {

    var b = new AIR2.UI.Button({
        air2type: 'CANCEL',
        text: 'Remove rule',
        iconCls: 'air2-icon-delete',
        handler : function(btn, ev) {
            var thisClause = btn.findParentBy(function(cmp) {
                //Logger(cmp);
                if (cmp.isaClause) {
                    return true;
                }
            });
            Logger("remove clause: ", thisClause);
            if (thisClause) {
                //Logger("looking for clause panel parent");
                var panel = btn.findParentBy(function(cmp) {                                              
                    if (cmp.isaClause && cmp.id != thisClause.id                                     
                        ||                                                                                
                        cmp.id == AIR2.Search.Advanced.FORMID
                    ) {                                                                                   
                        return true;                                                                      
                    }                                                                                     
                    //Logger(cmp);                                                                        
                    //else if (cmp                                                                        
                });
                if (panel) {
                    if (panel.isaClause) {
                        //Logger("parent panel: ", panel);
                        panel.remove(thisClause);
                        //Logger("after removing clause, item count ==" + panel.items.getCount());
                        if (panel.items.getCount() === 5) {
                            var anyAll = panel.find('isaAnyAll',true);
                            //Logger(anyAll);
                            panel.remove(anyAll[0]);
                        } 
                    }
                    else {
                        panel.remove(thisClause);
                    }
                }
            }
            
            AIR2.Search.Advanced.Explain();
            AIR2.Search.Advanced.getTermCount();
            
        },
        listeners : {
            'beforedestroy' : function(thisBox) {
                //Logger('destroyed button', thisBox);
                //return false;
            }
        }
    });
    
    return b;
}

AIR2.Search.Advanced.getFieldStore = function() {

    if (Ext.isDefined(AIR2.Search.Advanced.FIELDS)) {
        return AIR2.Search.Advanced.FIELDS;
    }

    var fields = AIR2.Search.Advanced.Fields[AIR2.Search.IDX];
    
    // mix in labels
    Ext.each(fields, function(item, idx, fieldList) {
        if (item.label) {
            return true;
        }
        item.label = (AIR2.Fixtures.FieldLabels[item.name]||item.name);
    });
    
    // sort by labels
    fields.sort(function(a,b) {
        var aLabel = a.label.toLowerCase();
        var bLabel = b.label.toLowerCase();
        if (aLabel < bLabel) { return -1 }
        if (aLabel > bLabel) { return 1  }
        return 0;
    });
    
    var fieldStore = [['', 'Any field', 'string']];
    Ext.each(fields, function(item, idx, fieldDefs) {
        fieldStore.push([ item.name, item.label, item.type, (item.list||null), (item.sortBy||null) ]);
    });
    
    AIR2.Search.Advanced.FIELDS = fieldStore;     //cache

    return fieldStore;
};

AIR2.Search.Advanced.Explain = function() {
    var dialect = Ext.getCmp(AIR2.Search.Advanced.EXPLAIN);
    dialect.getEl().dom.innerHTML = "<strong>Your search:</strong> " + dialect.stringify();
    var qlen = dialect.stringify().length;
    var searchBtn = Ext.getCmp('air2-adv-search-button');
    if (searchBtn.disabled) {
        if (qlen) {
            searchBtn.enable();
        }
        else {
            searchBtn.disable();
        }
    }
    else if (!qlen) {
        searchBtn.disable();
    }
        
}

AIR2.Search.Advanced.getTermCount = function() {
    if (AIR2.Search.Advanced.COUNT_IN_PROGRESS) {
        return;
    }
    
    var div = Ext.get('adv-search-counter');
    div.dom.innerHTML = AIR2.Search.SPINNER_IMG;   
    var dialect = Ext.getCmp(AIR2.Search.Advanced.EXPLAIN);
    var query  = dialect.stringify();
    if (!query.length) {
        div.dom.innerHTML = 0;
        return;
    }
    var masterBool = Ext.getCmp(AIR2.Search.Advanced.BOOLEAN).getValue();
    AIR2.Search.Advanced.COUNT_IN_PROGRESS = true;
    
    AIR2.Search.getCount({
        callback: function(json) {
            Logger("got count");
            AIR2.Search.Advanced.COUNT_IN_PROGRESS = false;
        },
        elId: 'adv-search-counter',
        url: AIR2.Search.URL+'.json?'+Ext.urlEncode({q:query, i:AIR2.Search.IDX, b:masterBool})
    });

}

AIR2.Search.Advanced.newClause = function(isFirst) {

    var removeButton = AIR2.Search.Advanced.RemoveButton();
    if (isFirst) {
        removeButton.disable();
    }
    var opList = AIR2.Search.Advanced.StringOpList();
    var textField = AIR2.Search.Advanced.TextField();
    var fieldStore = AIR2.Search.Advanced.getFieldStore();
    var fieldPicker = new Search.Query.FieldPicker({
        store: fieldStore,
        onReset: function() {
            // check if we have a combobox as our 3rd item
            // and restore it to a textfield if necessary.
            var clause = this.parentClause;
            if (!clause.getValueField().isaAdvTextField) {
                clause.remove(clause.getValueField());
                var tf = AIR2.Search.Advanced.TextField();
                tf.parentClause = clause;
                clause.insert(3, tf);
                clause.doLayout();
            }
        }
    });
    var boolPicker = new AIR2.UI.ComboBox({
        store: [
            ['AND','AND'] 
            ,['OR','OR']
            //,['NOT','NOT']    // too confusing for users
        ],
        width: 50,
        value: 'AND',
        triggerAction: 'all',
        selectOnFocus: true,
        getBoolean : function() {
            var val = this.getValue();
            //Logger("boolean == ", val);
            return val.toLowerCase();
        },
        listeners: {
            'select' : function(thisBox, rec, idx) {
                var v = thisBox.getBoolean();
                var clause = thisBox.parentClause;
                var prevValue = clause.bool;
                clause.removeFromDialect(prevValue);
                clause.addToDialect(v);
                clause.bool = v;
                AIR2.Search.Advanced.Explain();
                //Logger("clause bool set=="+v);
            }
        }
    });
    var items = [];
    items.push(fieldPicker);
    items.push(opList);
    items.push(textField);
    items.push('->');
    items.push(removeButton);
    
    var query = new Search.Query.Clause({
        items: items,
        explainer : function(clause) {
        
            //Logger("explainer:", clause);
            //Logger(clause.toJson());
            if (!Ext.isDefined(clause)) {
                throw new Ext.Error("clause required");
            }
            
            var div = Ext.get('adv-search-counter');
            div.dom.innerHTML = AIR2.Search.SPINNER_IMG;
        
            // show query
            AIR2.Search.Advanced.Explain();
            
            // get count
            if (!Ext.isDefined(clause.value) || 
                (!Ext.isObject(clause.value) && !clause.value.length)
            ) {
                Logger("no value in clause:", clause);
                div.dom.innerHTML = 0;
                return;
            }
            AIR2.Search.Advanced.getTermCount();               
        },
        
        getFieldPicker : function() {
            return this.get(0);
        },
        
        getOpList : function() {
            return this.get(1);
        },
        
        getValueField : function() {
            return this.get(2);
        },

        getRmButton : function() {
            return this.items.last();
        },
                
        getFieldFromValue : function(fieldValue) {
            var field;
            Ext.each(fieldStore, function(item,idx) {
                if (item[0] == fieldValue) {
                    field = item;
                    return false;
                }
            });
            if (!field) {
                //Logger("No such field: ", fieldValue);
                return;
            }
            return field;
        },
        
        onFieldChange : function(fieldValue) {
            
            // always reset if changing field, since opList will reset
            this.setOp('=');
            
            // find fieldValue in store and change textField if necessary
            //Logger(fieldStore);
            var field = this.getFieldFromValue(fieldValue);
            if (!field) {
                return;
            }
            var fieldType = field[2];
            var valuePicker = this.getValueField();
            if (fieldType == "auto") {
                if (valuePicker.isaAdvTextField) {
                
                    // change to a picklist
                    this.convertToPickList(field[3], field[4]);
                }
                else if (valuePicker.isaRangeField) {
                
                    // change to a picklist
                    this.convertToPickList(field[3], field[4]);
                
                }
                else if (valuePicker.isaNumberField) {
                
                    this.convertToPickList(field[3], field[4]);
                }
                else if (valuePicker.isaPickList) {
                
                    // change the picklist. 
                    // easiest to cheat and switch it to a text field first.
                    this.convertToTextField();
                    this.convertToPickList(field[3], field[4]);
                    
                }
            }
            else if (fieldType == "string") {
                this.convertToTextField();
            }
            else if (fieldType == "date") {
                this.convertToDateField();
            }       
            else if (fieldType == "int") {
                this.convertToNumberField();  
            }
            else if (fieldType == "form") {
                this.convertToFormPicker();
            }
            else {
                Logger("unknown fieldType:", fieldType);
            }
            
        },
        swapValueField : function(newVal) {
            this.remove(this.getValueField());
            this.insert(2, newVal);
        },
        swapOpList : function(newList) {
            this.remove(this.getOpList());
            this.insert(1, newList);
        },
        convertToFormPicker : function() {
            Logger("clause -> formPicker");
        
            var fp = new Ext.Panel({
                frame: false,
                border: false,
                isaClause: true,    // cheat for rmButton
                layout: 'column',
                bodyStyle: 'padding-top:3px',
                items: [
                    {
                        columnWidth: .80,
                        border: false,
                        frame: false,
                        items: [
                            new Ext.Panel({
                                frame       : false,
                                border      : false,
                                layout      : 'form',
                                labelAlign  : 'right',
                                style       : 'padding:4px',
                                labelWidth  : 60
                            })
                        ]
                    },
                    {
                        columnWidth: .20,
                        frame: false,
                        border: false,
                        bodyStyle: 'padding:0;padding-top:3px;padding-right:8px;text-align:right',
                        items: [
                            AIR2.Search.Advanced.RemoveButton()
                        ]
                    }
                ],
                disableRmButton : function() {
                    this.get(1).get(0).disable();
                },
                enableRmButton : function() {
                    this.get(1).get(0).enable();
                },
                setItemAt : function(item,at) {
                    var fItems = this.get(0).get(0).items;
                    var f = this.get(0).get(0);
                    if (fItems.get(at)) {
                        var items = fItems.getRange(at);
                        //Logger("remove:",items);
                        Ext.each(items, function(i,idx,arr) {
                            //Logger("removeAt",at+idx);
                            f.remove(i);
                        });
                    }
                    f.insert(at,item);
                },
                resetItemAt : function(at) {
                    var fItems = this.get(0).get(0).items;
                    var f = this.get(0).get(0);
                    if (fItems.get(at)) {
                        var items = fItems.getRange(at);
                        //Logger("remove:",items);
                        Ext.each(items, function(i,idx,arr) {
                            //Logger("removeAt",at+idx);
                            f.remove(i);
                        });
                        // if it was the last item, init a fresh one
                        if (at == 0) {
                            var clearBtn = Ext.getCmp('adv-search-clear-button');
                            clearBtn.handler(clearBtn);
                        }
                    }
                },
                resetProjectPicker : function() {
                
                    // same as clicking remove button
                    var rmBtn = this.get(1).get(0);
                    rmBtn.handler(rmBtn);
                },
                resetInquiryPicker : function() {
                    this.resetItemAt(1);
                    while (this.clause.value['and'].length > 1) {
                        this.clause.value['and'].pop();
                    }
                },
                resetQuestionPicker : function() {
                    this.resetItemAt(2);
                    while (this.clause.value['and'].length > 2) {
                        this.clause.value['and'].pop();
                    }
                },
                resetAnswerPicker : function() {
                    this.resetItemAt(3);
                    while (this.clause.value['and'].length > 3) {
                        this.clause.value['and'].pop();
                    }
                },
                setInquiryPicker : function(iP) {
                    this.setItemAt(iP,1);
                },
                setQuestionPicker : function(qP) {
                    this.setItemAt(qP,2);
                },
                setAnswerPicker : function(aP) {
                    this.setItemAt(aP,3);
                },
                stringify : function() {
                    Logger("stringify formpicker");
                    return this.clause.stringify();
                },
                setProjectQuery : function(v) {
                    var d = this.clause.value;
                    Logger("clause.value=",d);
                    if (!Ext.isDefined(d) || !Ext.isObject(d)) {
                        d = new Search.Query.Dialect({});    // make it a tree
                        this.clause.value = d;
                    }
                    var c = new Search.Query.Clause({});
                    c.field = 'project';
                    c.value = '"'+v+'"';
                    c.op    = '=';
                    d['and'] = [];  // reset no matter what
                    d['and'].push(c);
                },
                setInquiryQuery : function(v) {
                    var d = this.clause.value;
                    var c = new Search.Query.Clause({});
                    c.field = 'query';
                    c.value = v;
                    c.op    = '=';
                    while (d['and'].length > 1) {
                        d['and'].pop();
                    }
                    d['and'].push(c);
                },
                setQuestionQuery : function(v) {
                    var d = this.clause.value;
                    var c = new Search.Query.Clause({});
                    c.field = 'question';
                    c.value = v;
                    c.op    = '=';
                    while (d['and'].length > 2) {
                        d['and'].pop();
                    }
                    d['and'].push(c);
                },
                setAnswerQuery : function(v) {
                    var d = this.clause.value;
                    var c = new Search.Query.Clause({});
                    c.field = 'answer';
                    //Logger("answerQuery:", d);
                    c.value = '"'+v+'"';   // TODO this is broken??
                    c.op    = '=';
                    while (d['and'].length > 3) {
                        d['and'].pop();
                    }
                    d['and'].push(c);
                },
                setAnswerFullTextQuery : function(v) {
                    var d = this.clause.value;
                    var c = new Search.Query.Clause({});
                    c.field = 'answer';
                    c.value = '"'+v+'"';   // TODO this is broken??
                    c.op    = '=';
                    while (d['and'].length > 3) {
                        d['and'].pop();
                    }
                    d['and'].push(c);
                },
                setValue : function(v) {
                    this.setAnswerQuery(v);
                },
                updateDisplay : function() {
                    this.clause.updateDisplay();
                }
            });
            var picker = AIR2.Search.Inquiry.ChainedPicker(fp);
            var fPanel = Ext.getCmp(AIR2.Search.Advanced.FORMID);
            fp.get(0).get(0).add(picker);
            fp.clause = this;  //delegate to this for query generation
            
            // replace this clause with picker
            this.removeAll();   // kill kids first
            var thisIdx = fPanel.items.indexOf(this);
            fPanel.remove(this);    // removes from Dialect
            fp.clause.addToDialect(this.bool);   // add back to Dialect
            fp.clause.value = new Search.Query.Dialect({});    // make it a tree
            fPanel.insert(thisIdx, fp);
            fPanel.doLayout();
            
        },
        convertToRange : function() {
            Logger("convertToRange");
            var f = AIR2.Search.Advanced.RangeField();
            f.parentClause = this;
            var opList = AIR2.Search.Advanced.RangeOpList();
            opList.parentClause = this;
            this.swapValueField(f);
            this.swapOpList(opList);
            this.doLayout();
        },
        convertToDateField : function() {
            Logger("convertToDateField");
            var f = AIR2.Search.Advanced.DateRangeField();
            f.parentClause = this;
            var opList = AIR2.Search.Advanced.RangeOpList();
            opList.parentClause = this;
            this.setOp('..');  // always a range
            this.swapValueField(f);
            this.swapOpList(opList);
            this.doLayout();
        },
        convertToNumberField : function() {
            Logger("convertToNumberField");
            var f = AIR2.Search.Advanced.NumberField();
            f.parentClause = this;
            var opList = AIR2.Search.Advanced.OpList();
            opList.parentClause = this;
            this.swapValueField(f);
            this.swapOpList(opList);
            this.doLayout();
        },
        convertToTextField : function() {
            Logger("convertToTextField");
            var f = AIR2.Search.Advanced.TextField();
            f.parentClause = this;
            var opList = AIR2.Search.Advanced.StringOpList();
            opList.parentClause = this;
            this.swapValueField(f);
            this.swapOpList(opList);
            this.doLayout();
        },
        convertToPickList : function(array, sortBy) {
            
            var listStore = [];
            Ext.each(array(), function(item,idx,orig) {
                listStore.push(item[1]);
            });
            Logger("convertToPickList:", listStore);
            
            var pickList = new AIR2.UI.ComboBox({
                store: listStore,
                width: 200,
                triggerAction: 'all',
                selectOnFocus: true,
                isaPickList: true,
                forceSelection: true,
                listeners : {
                    'beforedestroy' : function(thisBox) {
                        //Logger('destroyed opList');
                        //return false;
                    },
                    'select' : function(thisBox, ev) {
                        //Logger(thisBox, "changed: ", thisBox.getValue());
                        // quote the value
                        thisBox.parentClause.value = '"' + thisBox.getValue() + '"';
                        thisBox.parentClause.updateDisplay();
                    }
    
                }
            });
            pickList.parentClause = this;
            // only = and != are valid
            var opList = AIR2.Search.Advanced.ListOpList();
            opList.parentClause = this;
            this.remove(this.getOpList());
            this.insert(1, opList);
            this.remove(this.getValueField());
            this.insert(2, pickList);
            this.doLayout();

        },
        onOpChange : function(opValue) {
            var fieldValue = this.getFieldPicker().getValue();
            var field = this.getFieldFromValue(fieldValue);
            if (!field) {
                return;
            }
            var fieldType = field[2];
            var valuePicker = this.getValueField();
            
            Logger("opValue change:", opValue);
            Logger("field:", field);
            Logger("opValue fieldType:", fieldType);
            Logger("valuePicker:", valuePicker);
            
            // range picker needed?
            if (opValue == ".." || opValue == "!..") {
                if (valuePicker.isaAdvTextField
                    ||
                    valuePicker.isaNumberField
                ) {
                    this.convertToRange(field[3], field[4]);
                
                }
                else if (valuePicker.isaPickList) {
                    
                
                }
            }
            else if (opValue == '=' || opValue == '!=') {
                
                if (valuePicker.isaRangeField && fieldType == 'string') {
                    this.convertToTextField();
                }
                else if (valuePicker.isaRangeField && fieldType == 'int') {
                    this.convertToNumberField();
                }
                else if (valuePicker.isaPickList) {                
                    
                
                }
            
            }
            else {
                // find fieldValue in store and change textField if necessary
            }
        },
        onValueChange : function(newVal) {
            var vf = this.getValueField();
            Logger(vf);
            if (!Ext.isDefined(vf)) {
                return;
            }
            vf.setValue(newVal);
        },
        removeFromDialect : function(bool) {
            var clause = this;
            var dialect = Ext.getCmp(AIR2.Search.Advanced.EXPLAIN);
            var clauses = [];
            for (var i=0; i<dialect[bool].length; i++) {
                if (dialect[bool][i].id == clause.id) {
                    continue;
                }
                clauses.push(dialect[bool][i]);
            }
            dialect[bool] = clauses;
            //Logger("dialect removed clause for bool=="+bool);
            //Logger(dialect);
        },
        addToDialect : function(bool) {
            var clause = this;
            var dialect = Ext.getCmp(AIR2.Search.Advanced.EXPLAIN);
            dialect[bool].push(clause);
            //Logger("dialect added clause for bool=="+bool);
            //Logger(dialect);
        },
        listeners : {
            'afterrender' : function(clause) {
                var bool = boolPicker.getBoolean();
                clause.addToDialect(bool);
                //Logger("add clause to dialect:", clause);
                clause.bool = bool;
            },
            'beforedestroy': function(clause) {
                var bool = boolPicker.getBoolean();
                clause.removeFromDialect(bool);
                //Logger("remove clause from dialect:",clause);
                return true; // proceed with destroy
            }
        },

        enableRmButton : function() {
            this.getRmButton().enable();
        },
        disableRmButton : function() {
            this.getRmButton().disable();
        }
    });
    return query;
};

AIR2.Search.Advanced.toggleRmButtons = function(sform) {
    //Logger(sform);
    var clauses = sform.getClauses();
    if (clauses.length === 1) {
        clauses[0].disableRmButton();
        return;
    }
    Ext.each(clauses, function(item,idx,len) {
        item.enableRmButton();
    });
    
}

AIR2.Search.Advanced.OpList = function() {

    var opList = new AIR2.UI.ComboBox({
        value : 'contains',
        width : 120,
        store : [
            ['=',  'contains'], 
            ['!=', 'does not contain'],
            ['..', 'includes'],
            ['!..','excludes']
        ],
        cls   : 'adv-search-ops',
        border: false,
        forceSelection: true,
        triggerAction: 'all',
        selectOnFocus: true,
        listeners : {
            'select' : function(thisBox, ev) {
                var v = thisBox.getValue();
                //Logger(thisBox, "changed: ", v);
                thisBox.parentClause.setOp(v);
                thisBox.parentClause.updateDisplay();
            }
        
        }
    });
    return opList;
    
}

AIR2.Search.Advanced.StringOpList = function() {

    var opList = new AIR2.UI.ComboBox({
        value : 'contains',
        width : 120,
        store : [
            ['=',  'contains'], 
            ['!=', 'does not contain']
        ],
        cls   : 'adv-search-ops',
        border: false,
        forceSelection: true,
        triggerAction: 'all',
        selectOnFocus: true,
        listeners : {
            'select' : function(thisBox, ev) {
                var v = thisBox.getValue();
                //Logger(thisBox, "changed: ", v);
                thisBox.parentClause.setOp(v);
                thisBox.parentClause.updateDisplay();
            }
        
        }
    });
    return opList;
    
}

AIR2.Search.Advanced.RangeOpList = function() {

    var opList = new AIR2.UI.ComboBox({
        value : 'includes',
        width : 120,
        store : [
            ['..', 'includes'],
            ['!..','excludes']
        ],
        cls   : 'adv-search-ops',
        border: false,
        forceSelection: true,
        triggerAction: 'all',
        selectOnFocus: true,
        listeners : {
            'select' : function(thisBox, ev) {
                var v = thisBox.getValue();
                //Logger(thisBox, "changed: ", v);
                thisBox.parentClause.setOp(v);
                thisBox.parentClause.updateDisplay();
            }
        
        }
    });
    return opList;
    
}

AIR2.Search.Advanced.ListOpList = function() {

    var opList = new AIR2.UI.ComboBox({
        value : 'is',
        width : 120,
        store : [
            ['=',  'is'],
            ['!=', 'is not']
        ],
        cls   : 'adv-search-ops',
        border: false,
        forceSelection: true,
        triggerAction: 'all',
        selectOnFocus: true,
        listeners : {
            'select' : function(thisBox, ev) {
                var v = thisBox.getValue();
                //Logger(thisBox, "changed: ", v);
                thisBox.parentClause.setOp(v);
                thisBox.parentClause.updateDisplay();
            }
        
        }
    });
    return opList;
    
}

AIR2.Search.Advanced.TextField = function() {

    var textField = new Ext.form.TextField({
        value : '',
        cls   : 'adv-search-textfield',
        border: false,
        enableKeyEvents: true,
        width: 450,
        isaAdvTextField : true,
        listeners : {
            'beforedestroy' : function(thisBox) {
                //Logger('destroyed textField');
                //return false;
            },
            'keyup' : function(thisBox, ev) {
                //Logger(thisBox, "changed: ", thisBox.getValue());
                //thisBox.parentClause.value = thisBox.getValue();
                thisBox.parentClause.setValue(thisBox.getValue()); // TODO ok to fire trigger?
                thisBox.parentClause.updateDisplay();
            }
        },
        tryQuery : function() {
            Logger('fire query');
        
        }
    });
    
    textField.on('specialkey', function(thisField, e){
        if (e.getKey() == e.ENTER) {
            thisField.tryQuery();
            e.stopEvent();
            e.stopPropagation();
        }
    });

    return textField;

}

AIR2.Search.Advanced.NumberField = function() {

    var textField = new Ext.form.TextField({
        value : '',
        vtype : 'Integer',
        cls   : 'adv-search-textfield',
        border: false,
        enableKeyEvents: true,
        width: 350,
        isaNumberField : true,
        listeners : {
            'keyup' : function(thisBox, ev) {
                //Logger(thisBox, "changed: ", thisBox.getValue());
                thisBox.parentClause.value = thisBox.getValue();
                thisBox.parentClause.updateDisplay();
            }
        },
        tryQuery : function() {
            Logger('fire query');
        
        }
    });
    
    textField.on('specialkey', function(thisField, e){
        if (e.getKey() == e.ENTER) {
            thisField.tryQuery();
            e.stopEvent();
            e.stopPropagation();
        }
    });

    return textField;

}

AIR2.Search.Advanced.RangeField = function() {

    var rangeField;
    rangeField = new Ext.form.CompositeField({
        value : [],
        cls   : 'adv-search-rangefield',
        border: false,
        enableKeyEvents: true,
        width: 380,
        isaRangeField : true,
        items: [
            new Ext.form.Label({
                text: 'Min',
                style: 'padding: 4px'
            }),
            new Ext.form.TextField({
                width: 140,
                vtype: 'Integer',
                enableKeyEvents: true,
                validator : function(curVal) {
                    if (Ext.isDefined(rangeField.parentClause.value[1])) {
                        Logger("Min is defined");
                        if (parseInt(rangeField.parentClause.value[1]) <= parseInt(curVal)) {
                            return "Value must be greater than " + rangeField.parentClause.value[0];
                        }
                        else if (rangeField.parentClause.value[1].length != curVal.length) {
                            return "Must be same length as " + rangeField.parentClause.value[0];
                        }
                    }
                    return true;
                },
                listeners: {
                    'keyup': function(thisField, e) {
                        Logger("min:", thisField.getValue());
                        if (rangeField.parentClause.value === "") {
                            rangeField.parentClause.value = [];
                        }
                        rangeField.parentClause.value[0] = thisField.getValue();
                        rangeField.parentClause.updateDisplay();
                    }
                }
            }),
            new Ext.form.Label({
                text: '.. Max',
                style: 'padding: 4px'
            
            }),
            new Ext.form.TextField({
                width: 140,
                vtype: 'Integer',
                enableKeyEvents: true,
                validator : function(curVal) {
                    if (Ext.isDefined(rangeField.parentClause.value[0])) {
                        Logger("Max is defined");
                        if (parseInt(rangeField.parentClause.value[0]) >= parseInt(curVal)) {
                            return "Value must be greater than " + rangeField.parentClause.value[0];
                        }
                        else if (rangeField.parentClause.value[0].length != curVal.length) {
                            return "Must be same length as " + rangeField.parentClause.value[0];
                        }
                    }
                    return true;
                },
                listeners: {
                    'keyup': function(thisField, e) {
                        Logger("max:", thisField.getValue());
                        if (rangeField.parentClause.value === "") {
                            rangeField.parentClause.value = [];
                        }
                        rangeField.parentClause.value[1] = thisField.getValue();
                        rangeField.parentClause.updateDisplay();
                    }
                }
            })
        ],
        tryQuery : function() {
            Logger('fire query');
            this.items.each(function(item, idx, len) {
                Logger(item.getValue());
            });
        }
    });
    
    rangeField.on('specialkey', function(thisField, e){
        if (e.getKey() == e.ENTER) {
            thisField.tryQuery();
            e.stopEvent();
            e.stopPropagation();
        }
    });
    
    return rangeField;

}

AIR2.Search.Advanced.DateRangeField = function() {
    
    var rangeField;
    rangeField = new Ext.form.CompositeField({
        value : [],
        cls   : 'adv-search-daterangefield',
        border: false,
        enableKeyEvents: true,
        width: 380,
        isaRangeField : true,
        items: [
            new Ext.form.Label({
                text: 'Start',
                style: 'padding: 4px'
            }),
            new Ext.form.DateField({
                width: 140,
                format: 'Ymd',
                enableKeyEvents: true,
                updateDialect : function(thisField) {
                    if (rangeField.parentClause.value === "") {
                        rangeField.parentClause.value = [];
                    }
                    rangeField.parentClause.value[0] = thisField.getValue().format('Ymd');
                    rangeField.parentClause.updateDisplay();
                },
                listeners: {
                    'select': function(thisField, e) {
                        Logger("start:", thisField.getValue().format('Ymd'));
                        thisField.updateDialect(thisField);
                    },
                    'keyup': function(thisField, e) {
                        Logger("start:", thisField.getValue().format('Ymd'));
                        thisField.updateDialect(thisField);
                    }
                }
            }),
            new Ext.form.Label({
                text: '.. End',
                style: 'padding: 4px'

            }),
            new Ext.form.DateField({
                width: 140,
                format: 'Ymd',
                enableKeyEvents: true,
                // TODO validator
                updateDialect : function(thisField) {
                    if (rangeField.parentClause.value === "") {
                        rangeField.parentClause.value = [];
                    }
                    rangeField.parentClause.value[1] = thisField.getValue().format('Ymd');
                    rangeField.parentClause.updateDisplay();
                },
                listeners: {
                    'select': function(thisField, e) {
                        Logger("end:", thisField.getValue().format('Ymd'));
                        thisField.updateDialect(thisField);
                    },
                    'keyup': function(thisField, e) {
                        Logger("end:", thisField.getValue().format('Ymd'));
                        thisField.updateDialect(thisField);
                    }
                }
            })
        ],
        tryQuery : function() {
            Logger('fire query');
            this.items.each(function(item, idx, len) {
                Logger(item.getValue());
            });
        }
    });
    
    rangeField.on('specialkey', function(thisField, e){
        if (e.getKey() == e.ENTER) {
            thisField.tryQuery();
            e.stopEvent();
            e.stopPropagation();
        }
    });

    return rangeField;
}

AIR2.Search.Advanced.Panel = function(cfg) {

    var boolPicker = new AIR2.UI.ComboBox({
        store: [
            ['AND','AND'],['OR','OR']
        ],
        width: 50,
        value: 'AND',
        id: AIR2.Search.Advanced.BOOLEAN,
        triggerAction: 'all',
        selectOnFocus: true,
        getBoolean : function() {
            var val = this.getValue();
            //Logger("boolean == ", val);
            return val.toLowerCase();
        },
        listeners: {
            'select' : function(thisBox, rec, idx) {
                var dialect = Ext.getCmp(AIR2.Search.Advanced.EXPLAIN);
                dialect.default_bool = this.getValue();
                dialect.op_map['and'] = ' '+this.getValue()+' ';    // HACK!!
                AIR2.Search.Advanced.Explain();
                AIR2.Search.Advanced.getTermCount();
            }
        }
    });

    var footer = new Ext.Toolbar({
        style: "background:#eee;border:none;",
        items: [
            {
                id: 'adv-search-clear-button',
                xtype: 'air2button',
                iconClass: 'air2-icon-clear',
                air2type: 'BLUE',
                text: 'Clear',
                handler: function(btn, ev) {
                    // clear global dialect object -- IMPORTANT to do this before FORMID
                    var dialect = Ext.getCmp(AIR2.Search.Advanced.EXPLAIN);
                    //Logger("dialect before clear():" + dialect, dialect);
                    dialect.clear();
                    //Logger("dialect after clear():" + dialect, dialect);

                    // get parent, delete all but first queryclause
                    var parentForm = Ext.getCmp(AIR2.Search.Advanced.FORMID);
                    //Logger(parentForm);
                    var clauses = parentForm.getClauses();
                    Ext.each(clauses, function(item, idx, len) {
                        if (item.isaClause) {
                            parentForm.remove(item);
                        }
                    });
                    // insert one fresh clause
                    parentForm.add(AIR2.Search.Advanced.newClause(true));
                    parentForm.doLayout();
                    
                    AIR2.Search.Advanced.Explain();
                    Ext.get('adv-search-counter').dom.innerHTML = '0';
                    
                }
            },
            '->',
            {
                text: 'Search',
                disabled: true, // until a query is created
                xtype: 'air2button',
                id: 'air2-adv-search-button',
                iconClass: 'air2-icon-search',
                air2type: 'BLUE',
                handler: function(btn, ev) {
                    var dialect = Ext.getCmp(AIR2.Search.Advanced.EXPLAIN);
                    var query = dialect.stringify();
                    //Logger("adv query="+query);
                    var masterBool = Ext.getCmp(AIR2.Search.Advanced.BOOLEAN).getValue();
                    var uri = AIR2.Search.URL+'/'+AIR2.Search.IDX+'?'+Ext.urlEncode({q:query, b:masterBool});
                    Logger(uri);
                    window.location = uri;
                }
            }
        ]
    });
        
    var sform = new Ext.form.FormPanel({
        id:         AIR2.Search.Advanced.FORMID,
        xtype:      'form',
        cls:        'adv-search-form',
        border:     false,
        listeners: {
            add : function(thisForm,theClause,idx) {
                //Logger(thisForm);
                //Logger(theClause);
                //Logger("add clause at ",idx);
                if (theClause.isaClause) {
                    AIR2.Search.Advanced.toggleRmButtons(thisForm);
                }
            },
            remove : function(thisForm,theClause) {
                //Logger("rm clause", theClause);
                if (theClause.isaClause) {
                    AIR2.Search.Advanced.toggleRmButtons(thisForm);
                }
            }
        },
        getClauses : function() {
            var c = [];
            this.items.each(function(item) {
                if (item.isaClause) {
                    c.push(item);
                }
            });
            return c;
        },
        addClause: function() {
            var isFirst = this.getClauses().length ? false : true;
            //Logger("addClause isFirst=="+isFirst);
            var newClause = AIR2.Search.Advanced.newClause(isFirst);
            this.add(newClause);
            this.doLayout();
        },
        items: [
            {
                xtype: 'toolbar',
                border: false,
                style: "background:#eee;",
                cls:   "adv-search-header",
                items: [
                    {
                        xtype:      'air2button',
                        iconCls:    'air2-icon-add',
                        air2type:   'BLUE',
                        text:       'Add rule',
                        id:         'air2-advsearch-add-rule-button',
                        handler: function(btn,ev) {
                            var panel = btn.ownerCt.ownerCt;
                            //Logger(panel);
                            panel.addClause();
                        }
                    },
                    ' ',
                    '-',
                    ' ',
                    'Join rules with:',
                    boolPicker,
                    '->',
                    'Number of results: <span id="adv-search-counter">0</span>'
                ],
                listeners : {
                    'render' : function(thisToolbar) {
                        //Logger("rendered toolbar");
                    }
                }
            }
        ]
    });
    
    var dialect = new Search.Query.Dialect({
        id: AIR2.Search.Advanced.EXPLAIN,
        html: '<strong>Your search</strong>: ()',
        cls: 'query-explain'
    });
    
    var panel = new Ext.Panel({
        border: false,
        items: [
            sform,
            footer,
            dialect
        ]        
    });

    return panel;
}
